package cn.thens.okbinder2;

import ohos.rpc.IRemoteObject;
import ohos.rpc.RemoteObject;
import java.lang.reflect.Method;
import java.security.MessageDigest;
import java.util.*;

@SuppressWarnings({"ConstantConditions", "unchecked"})
public final class OkBinder {
    static final String TAG = "@OkBinder";
    private static final Map<Class<?>, OkBinderFactory> FACTORIES = new HashMap<>();
    private static final OkBinderFactory DEFAULT_FACTORY = new ReflectionFactory();

    /**
     * 构造
     *
     * @param remoteObject object类型
     * @return 客户端
     */
    public static RemoteObject create(Object remoteObject) {
        Class<?> okBinderInterface = getOkBinderInterface(remoteObject);
        require(okBinderInterface != null,
                "Remote object must implement only one interface with @AIDL annotation");
        return create((Class<Object>) okBinderInterface, remoteObject);
    }

    /**
     * 创建
     *
     * @param okBinderInterface 类
     * @param remoteObject 数据
     * @param <T> 任何类型
     * @return 获取RemoteObject
     */
    public static <T> RemoteObject create(Class<T> okBinderInterface, T remoteObject) {
        return getFactory(okBinderInterface).newBinder(okBinderInterface, remoteObject);
    }

    /**
     * 代理
     *
     * @param serviceClass 类
     * @param binder 服务
     * @param <T> 任何类型
     * @return 返回代理
     */
    public static <T> T proxy(Class<T> serviceClass, IRemoteObject binder) {
        if (binder instanceof OkBinderFactory.BaseBinder) {
            return (T) ((OkBinderFactory.BaseBinder) binder).getRemoteObject();
        }
        return (T) getFactory(serviceClass).newProxy(serviceClass, binder);
    }

    private static OkBinderFactory getFactory(Class<?> serviceClass) {
        require(isOkBinderInterface(serviceClass),
                "Service class must be an interface with @AIDL annotation");
        OkBinderFactory factory = FACTORIES.get(serviceClass);
        if (factory != null) {
            return factory;
        }
        String factoryClassName = serviceClass.getName() + "Factory";
        try {
            ClassLoader classLoader = serviceClass.getClassLoader();
            Class<?> factoryClass = classLoader.loadClass(factoryClassName);
            factory = (OkBinderFactory) factoryClass.newInstance();
        } catch (Throwable e) {
        }
        if (factory == null) {
            factory = DEFAULT_FACTORY;
        }
        FACTORIES.put(serviceClass, factory);
        return factory;
    }

    private static void require(boolean isRequire, String message) {
        if (!isRequire) {
            throw new IllegalArgumentException(message);
        }
    }

    static boolean isOkBinderInterface(Class<?> cls) {
        return cls.isInterface() && cls.isAnnotationPresent(AIDL.class);
    }

    static Class<?> getOkBinderInterface(Object object) {
        Class<?>[] interfaces = object.getClass().getInterfaces();
        List<Class<?>> okBinderInterfaces = new ArrayList<>();
        for (Class<?> anInterface : interfaces) {
            if (isOkBinderInterface(anInterface)) {
                okBinderInterfaces.add(anInterface);
            }
        }
        if (okBinderInterfaces.size() != 1) {
            return null;
        }
        return okBinderInterfaces.get(0);
    }

    /**
     * 获取功能
     *
     * @param method 函数
     * @return base64数据
     */
    public static String getFunctionId(Method method) {
        StringBuilder functionId = new StringBuilder(method.getName());
        StringBuilder params = new StringBuilder();
        boolean isFirst = true;
        for (Class<?> paramType : method.getParameterTypes()) {
            params.append(isFirst ? "" : ",").append(paramType.getName());
            isFirst = false;
        }
        if (params.length() <= 24) {
            return functionId.append("(").append(params).append(")").toString();
        }
        try {
            byte[] bytes = params.toString().getBytes();
            byte[] md5 = MessageDigest.getInstance("SHA-512").digest(bytes);
            String base64 = Base64.getEncoder().encodeToString(md5);
            return functionId.append("(").append(base64).append(")").toString();
        } catch (Throwable e) {
            throw ErrorUtils.wrap(e);
        }
    }
}
